//*************************************************************************************
// File:     EventDispatcher.cs
//*************************************************************************************
// Description: Clase sealed que representa un despacho de eventos GDESK.
//*************************************************************************************
// Classes:      EventDispatcher : IDisposable
//*************************************************************************************
// Delegates:   void ProcessEventDelegate(XGDESKEvent)
//*************************************************************************************
// Use:         XGDESK asigna una funcion delegada para el proceso de los eventos 
//              lanzados por el despacho.
//*************************************************************************************
// Author:      ManOguaR
//*************************************************************************************

using Xeralabs.Core.Events;
using Xeralabs.Core.Utils.Timing;
using System;
using System.Collections.Generic;
using System.Threading;

namespace Xeralabs.Core.Internals
{
    /// <summary>
    /// Delegado para el proceso de un evento.
    /// </summary>
    /// <param name="evt">El evento a procesar.</param>
    public delegate void ProcessEventDelegate(XGDESKEvent evt);

    /// <summary>
    /// Representa un despacho de eventos para GDESK.
    /// </summary>
    public sealed class EventDispatcher : IDisposable
    {
        private EventQueue _eventQueue = null;

        private event ProcessEventDelegate _dispatchEvent = null;
        private HiResTimer _statsTimer = null;

        private AutoResetEvent _autoReset;
        private Thread _localThread = null;

        private bool _running;
        private bool _initialized;
        private bool _ending;

        private int _timerIndex = -1;
        private int _nextTemporalId = 0;
        private List<int> _freeTemporalIds;

        private float _evtsPerSecond = 0;
        private float _evtsPerSecondRecord = 0;
        private float _evtsPerSecondAverage = 0;
        private int _evtCounter = 0;
        private double _secondCounter = 0;

        /// <summary>
        /// Devuelve si la instancia ha sido inicializada.
        /// </summary>
        public bool IsInit { get { return _initialized; } }
        /// <summary>
        /// Devuelve si el despacho de eventos se encuentra en funcionamiento.
        /// </summary>
        public bool Running { get { return _initialized && _running; } }
        /// <summary>
        /// Establece el delegado para despachar eventos del kernel de simulacion.
        /// </summary>
        public ProcessEventDelegate DispatchEvent { set { _dispatchEvent = value; } }

        /// <summary>
        /// Constructor.
        /// </summary>
        public EventDispatcher()
        {
            this._running = false;
            this._initialized = false;

            this._statsTimer = new HiResTimer();
        }

        /// <summary>
        /// Inicializa el despacho de eventos con un valor por defecto.
        /// </summary>
        /// <returns>True: el despacho se inicializo con exito; false: no pudo inicializarse el despacho.</returns>
        public bool InitializeDispatcher()
        {
            return this.InitializeDispatcher(10);
        }
        /// <summary>
        /// Inicializa el despacho de eventos con un valor indicado por el parametro <see cref="queueSize"/>.
        /// </summary>
        /// <param name="queueSize">El valor inicial de la cola ordenada de eventos.</param>
        /// <returns>True: el despacho se inicializo con exito; false: no pudo inicializarse el despacho.</returns>
        public bool InitializeDispatcher(int queueSize)
        {
            if (this._eventQueue != null)
                return false;

            this._localThread = new Thread(new ThreadStart(this.DispatcherLoop));
            this._autoReset = new AutoResetEvent(false);
            this._eventQueue = new EventQueue(queueSize);

            this._ending = false;
            this._running = false;

            this._nextTemporalId = 0;
            this._freeTemporalIds = new List<int>(queueSize);

            this._timerIndex = -1;

            return this._initialized = true;
        }

        /// <summary>
        /// Comienza la ejecucion del despacho de eventos.
        /// </summary>
        public int StartDispatcher()
        {
            if (_initialized)
            {
                //if (GlobalTimer.IsStopped)
                //{
                //    GlobalTimer.Reset();
                //    GlobalTimer.Start();
                //}
                this._timerIndex = KernelTimer.StartTimer();

                if (_timerIndex > -1)
                {
                    _running = true;
                    _localThread.Start();
                }
            }
            return _timerIndex;
        }
        /// <summary>
        /// Detiene el despacho de eventos.
        /// </summary>
        /// <param name="end">Indica si debe finalizarse despues de la detencion.</param>
        public void StopDispatcher(bool end)
        {
            //TODO: STOP SIMULATION OPS.
            this._ending = end;

            try
            {
                _localThread.Abort();
            }
            catch (ThreadStateException)
            {
                //TODO: thread is suspended.
            }
        }

        [MTAThread]
        private void DispatcherLoop()
        {
            _evtCounter = 0;
            _evtsPerSecond = 0.0f;
            _secondCounter = 0.0;


            try
            {
                double nextTimeMark;
                int waitTimeMs;

                Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
#if DEBUG
                System.Diagnostics.Debug.WriteLine(String.Format("PRIORITY - MAIN LOOP: {0}", Thread.CurrentThread.ManagedThreadId));
#endif
                Monitor.Enter(_eventQueue);
                this._statsTimer.Start();
                this._statsTimer.Reset();

                while (_running)
                {
                    _secondCounter += _statsTimer.GetElapsedTime();
                    if (_secondCounter > 1000.0)
                    {
                        int seconds = (int)(_secondCounter / 1000.0);
                        _evtsPerSecond = (float)_evtCounter / (float)seconds;
                        if (_evtsPerSecond > _evtsPerSecondRecord)
                            _evtsPerSecondRecord = _evtsPerSecond;
                        _evtCounter = 0;
                        _secondCounter = 0.0;

                        _evtsPerSecondAverage += _evtsPerSecond;

                        if (_evtsPerSecondAverage != _evtsPerSecond)
                            _evtsPerSecondAverage /= 2.0f;
                    }
                    nextTimeMark = _eventQueue.PeekTime();

                    while (nextTimeMark == -1.0)
                    {
                        this._autoReset.WaitOne(-1);
                        nextTimeMark = _eventQueue.PeekTime();
                    }
                    while ((waitTimeMs = (int)((nextTimeMark - KernelTimer.GetTime(_timerIndex)) * 1000)) > 0)
                    {
                        this._autoReset.WaitOne(waitTimeMs);
                        nextTimeMark = _eventQueue.PeekTime();
                    }


                    if (nextTimeMark <= KernelTimer.GetTime(_timerIndex))
                    {
                        if (_dispatchEvent != null)
                        {
                            try
                            {
                                this._dispatchEvent(_eventQueue.DequeueEvent());
                                _evtCounter++;
                            }

                            catch (InvalidOperationException)
                            {
                                //TODO: Big Error.
                            }
                        }
                    }
                }
            }
            catch (ThreadInterruptedException)
            {
                //TODO: Administrar interrupcion en estado de espera.
            }
            catch (ThreadAbortException)
            {
                //Monitor.Exit(_eventQueue);
                //TODO: Administrar llamada a _localThread.Abort()
                this.EndDispatcher();
            }
            catch (ThreadStateException)
            {
                //TODO: Administrar estado de hilo de proceso invalido.
            }
        }

        private void EndDispatcher()
        {
            //TODO: END SIMULATION OPS.
            this._initialized = false;
            this._running = false;

            KernelTimer.StopTimer(_timerIndex);
            _timerIndex = -1;

            if (_localThread != null)
                this._localThread = null;

            this._nextTemporalId = 0;

            if (_ending)
            {
                try
                {
                    this._eventQueue.Dispose();
                    this._eventQueue = null;
                    this._freeTemporalIds = null;
                }
                catch (InvalidOperationException)
                {

                }
            }
            else
            {
                this._localThread = new Thread(new ThreadStart(this.DispatcherLoop));

                this._freeTemporalIds.Clear();

                this._initialized = true;
            }
        }

        /// <summary>
        /// Inserta un evento en la cola de eventos.
        /// </summary>
        /// <param name="evt">El evento a insertar.</param>
        public int PushEvent(XGDESKEvent evt)
        {
            if (evt == null)
                throw new ArgumentNullException("evt");

            int res = -1;

            if (_initialized)
            {                
                _eventQueue.EnqueueEvent(evt);
                _autoReset.Set();

                if (_freeTemporalIds.Count > 0)
                {
                    res = _freeTemporalIds[_freeTemporalIds.Count - 1];
                    _freeTemporalIds.RemoveAt(_freeTemporalIds.Count - 1);
                }
                else
                {
                    res = _nextTemporalId;
                    _nextTemporalId++;
                }
            }

            return res;
        }
        /// <summary>
        /// Metodo para retirar todos los eventos pendientes relacionados a la entidad emisora identificada por el parametro <see cref="entityGUID"/>.
        /// </summary>
        /// <param name="entityGUID">El identificador unico de 64 bits que representa la entidad emisora de eventos.</param>
        /// <returns>True: no quedan eventos con emisor equivalente a <see cref="entityGUID"/>.</returns>
        public List<XGDESKEvent> RemoveEvents(ulong entityGUID)
        {
            if (entityGUID == 0)
                throw new ArgumentNullException("entityGUID");

            return this._eventQueue.RemoveEvents(entityGUID);
        }

        /// <summary>
        /// Libera todos los recursos utilizados por el despacho de eventos.
        /// </summary>
        public void Dispose()
        {
            if (_running)
                throw new InvalidOperationException();

            _dispatchEvent = null;
            _localThread = null;
        }
    }
}